<?php
/**
 *  Form elements can be of 2 kinds: standard and custom.
 *  Standard form elements are those with class names Yz_Form_Element_*.
 *  These are not subject to class name resolution.
 *
 *  Form is iterateable over its elements which are stored in __dict__
 *
 *  @todo default action derived from module name
 *  @todo element classes must be inheritable
 *
 */
abstract class Yz_Form_Abstract
    extends Yz_Object
    implements Yz_Form_Interface
{

    /** predefined elements
     *
     */
    #const ELEMENT_ID = false; // no form element for id

    /**
     *  @var string
     */
    protected $name;
    /**
     *  @var string
     */
    protected $action = "";
    /**
     *  @var string
     */
    protected $method = 'POST';


    /** The form's elements: reference to $__dict__
     *  @var array
     */
    protected $elements;

    /**
     *  @var array
     */
    protected $parseErrors = array();

    /**
     *  @var Yz_Module_Interface
     */
    protected $module;

    /**
     *  @var ReflectionClass
     */
    protected $reflection;
    /** set custom renderer
     *  @var null
     */
    protected $renderer;
    /** set custom parser
     *  @var null
     */
    protected $parser;
    /** item being edited/added
     *  @var Yz_Item_Interface
     */
    protected $item;


    /****************************
     *      Public methods      *
     ****************************/

    /**
     *  @param module
     *  @param model
     *
     */
    public function __construct( Yz_Module_Interface $module, Yz_Item_Interface $item )
    {
        debug();
        $this->name = lcfirst($this->_name_());
        $this->reflection = new ReflectionClass($this);
        $this->module = $module;
        $this->elements =& $this->__dict__;

        $this->assignElements( $item );
    }


    /**
     *  @param Yz_Item_Interface $item
     *      Params are passed to element constructor
     *  @return chainable
     */
    public function assignElements( Yz_Item_Interface $item )
    {
        debug();
        $this->item = $item;
        foreach ($item->describe() as $name => $prop)
            $el = $this->createElement($name, $prop)
                and $this->__dict__[$name] = $el
            ;
        return $this;
    }

    /**
     *  @return string form's class name
     */
    final public function __toString()
    {
        $c = $this->_class_();
        return "{$c}('{$this->name}')";
    }

    /** get the module the form belongs to
     *  Also a getter may be used: x = $form->module
     *  @return Yz_Module_Interface
     */
    final public function getModule()
    {
        return $this->module;
    }

    /**
     *  Also a getter may be used: x = $form->name
     *
     */
    final public function getName()
    {
        return $this->name;
    }

    /**
     *  Also a setter may be used: $form->name
     *
     */
    final public function setName($name)
    {
        $this->name = Yz_String::test($name);
        return $this;
    }

    /**
     *  Also a getter may be used: x = $form->action
     *
     */
    final public function getAction()
    {
        return $this->action;
    }

    /**
     *  Also a setter may be used: $form->action
     *
     */
    final public function setAction($action)
    {
        $this->action = Yz_String::test($action);
        return $this;
    }

    /**
     *  Also a getter may be used: x = $form->action
     *
     */
    final public function getMethod()
    {
        return $this->method;
    }

    /**
     *  Also a setter may be used: $form->method
     *
     */
    final public function setMethod($method)
    {
        $this->method = Yz_String::test($method);
        return $this;
    }

    /**
     *  return copy of elements array
     *  @return Yz_Hash
     */
    final public function getElements()
    {
        return new Yz_Hash($this->__dict__);
    }

    /**
     *  return the assigned 'values' i.e. item
     *  @return Yz_Item_Interface
     */
    final public function getValues()
    {
        return $this->item;
    }

    /** Assign new values to form
     *      NOTE: originally, assignment is done in constructor
     *  @param Yz_Item_Interface $values
     */
    public function assignValues(Yz_Item_Interface $item)
    {
        debug();
        $this->item = $item;
        return $this;
    }

    /**
     *  A renderer defines:
     *  - the visual wrapping ("design") of form elements
     *  - the order of elements
     *  - the relationships among elements (eg. If A=x, skip B)
     *  - the SUBMIT element
     */
    public function render(Yz_Template_Interface $template)
    {
        debug();
        $class = $this->identifyClass('Renderer', $this->renderer);
        $r = new $class($this);

        // set values to render, unless we are rendering a form parsed with errors
        if (! $this->parseErrors)
            $r->setValues($this->item);

        return $r
            ->render($template)
        ;
    }


    /**
     *  A parser defines:
     *  - the "requiredness" of elements
     *  - the relationships among elements (eg. If A=x, skip B)
     *  - external parameters which affect the parsing (resubmission, delays, etc)
     */
    public function parse(Yz_Hash $input)
    {
        debug();
        $class = $this->identifyClass('Parser', $this->parser);
        $p = new $class($this);
        return $p->parse($input); // throws on error
    }



    /****************************
     *      Protected methods   *
     ****************************/

    /** create form element with name, using optional configuration:
     *      null        ->
     *      string      -> element type name
     *      Yz_Class    -> configuration object, with formElementType property for element type
     *  @param string $name
     *  @param mixed $config
     *  @return Yz_Form_Element_Interface
     */
    protected function createElement($name, $type)
    {
        debug("Creating element for field '$name'");
        $el = null;
        $refl = $this->reflection;
        $const = 'ELEMENT_' . strtoupper($name);
        if ($refl->hasConstant($const)) {
            $elType = $refl->getConstant($const);
            if (! $elType)
                return null;
        } else if ($type instanceof Yz_Model_Field_Interface) {
            $elType = $type->_name_();
        } else {
            $elType = (string)$type;
        }
        $class = $this->identifyClass('Element', $elType);
        $el = new $class($this, $name, $type);
        return $el;
    }


    /** identify the class name of component (element, parser, renderer)
     *      either by a class constant or by module.resolve
     *  @param string $libType
     *  @param string|null $libName [Default]
     *  @return string class name
     *  @throw InvalidArgument if $libName is not a valid identifier
     */
    protected function identifyClass($libType, $libName)
    {
        if (null === $libName)
            $libName = 'Default';

        $class = "Form_{$libType}_{$libName}";
        $class = $this->module->resolveClass($class, 'Yz_'.$class);
        return $class;
    }

}
